import {
    AxiosResponse,
    HttpAxiosReponseData,
    HttpAxiosData,
    PropsSignautre,
} from './interface';
import axios from 'axios';
import {stringify} from 'qs';

const instance = axios.create();

/**
 * 请求之前的处理
 */
instance.interceptors.request.use(
    (config: any) => config, // 发送请求之前做一些处理
    (error: Error) => Promise.reject(error),
);

/**
 * 请求完成之后的处理
 */
instance.interceptors.response.use(
    (response: AxiosResponse | any) => response.data, // 只给用户反回服务端的数据
    (error: any) => {
        const errstr =
            error && error.response
                ? statusToText(error.response.status)
                : '连接到服务器失败'; // 请求错误的描述信息
        
        return Promise.reject({
            errNo: 1,
            errResponse: error.response,
            errstr,
        });
    },
);

/**
 * 发送网络请求
 * @param {Object} api 请求的基本信息
 * @param {String} api.method 请求类型 get|post 默认post
 * @param {String} api.url 请求地址
 * @param {Object} data 请求的数据 默认{}
 * @param {Object} options 请求的设置 可选
 * @param {Boolean} options.withCredentials 是否添加跨域cookie 默认false
 * @param {Number} options.timeout 超时时间，单位ms，默认10000
 */
export const $http = (
    api: any,
    data?: HttpAxiosData,
    options?: any,
): Promise<HttpAxiosReponseData> => {
    options = {...options}; // 这个操作可使undefined之流变为{}
    // 公共参数
    const httpDefaultOpts = {
        /* 请求协议 */
        method: api.method.toLowerCase() || 'post',
        /* 基础 url 前缀 */
        baseURL: '',
        /* 请求的地址 */
        url: api.url,
        /* 超时时间, 单位毫秒 */
        timeout: options.timeout || 10000,
        /* 跨域请求时是否需要使用凭证 */
        withCredentials: options.withCredentials || false,
        /* 请求头信息 */
        headers:
            api.method.toLowerCase() === 'get'
                ? {
                    'X-Requested-With': 'XMLHttpRequest',
                    Accept: 'application/json',
                }
                : {
                    'X-Requested-With': 'XMLHttpRequest',
                    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
                },
    };
    // 添加请求数据
    const baseRequestData = getBaseData(); // 基础参数
    const jsonData = transformData(data); // 经过处理后的业务参数
    if (httpDefaultOpts.method === 'get') {
        /* get请求的参数，是用户数据和基础参数的并集 */
        (httpDefaultOpts as any).params = {...baseRequestData, ...jsonData};
    } else {
        /* post 请求的数据主体 */
        (httpDefaultOpts as any).data = stringify({
            ...baseRequestData,
            ...jsonData,
        });
    }
    
    // 提供修改默认参数的入口, 目前采用浅复制
    const config = {...httpDefaultOpts, ...options};
    // 发起请求
    return new Promise<HttpAxiosReponseData>((resolve, reject) => {
        instance(config)
            .then((res: HttpAxiosReponseData | any) => {
                resolve(res);
            })
            .catch((error: Error) => {
                reject(error);
            });
    });
};


function getBaseData(): any {
    const baseData = {
        _: new Date().getTime(), // 时间戳
    };
    return baseData;
}

/**
 * 将错误码转化为描述的文字
 * @param status http请求错误码
 */
function statusToText(status: number): string {
    return (
        ({
            '400': '错误请求',
            '401': '未授权，请重新登录',
            '403': '拒绝访问',
            '404': '请求错误,未找到该资源',
            '405': '请求方法未允许',
            '408': '请求超时',
            '500': '服务器端出错',
            '501': '网络未实现',
            '502': '网络错误',
            '503': '服务不可用',
            '504': '网络超时',
            '505': 'http版本不支持该请求',
        } as PropsSignautre)[status] || '连接错误!' + status
    );
}

/**
 * 如果参数是FormData或者URLSearchParams类型，不能直接与json数据合并，需要转化成json数据。
 * @param data 参数数据
 */
function transformData(data: HttpAxiosData = {}): PropsSignautre {
    let jsonData: PropsSignautre = {};
    
    const objectType: string = Object.prototype.toString.call(data).slice(8, -1);
    if (objectType === 'URLSearchParams' || objectType === 'FormData') {
        for (const item of data as FormData | URLSearchParams) {
            jsonData[item[0]] = item[1]; // 如果存在同名属性，后者覆盖前者
        }
    } else {
        jsonData = data;
    }
    
    return jsonData;
}
